﻿#region License
/* Copyright (C) 2009 Tim Coulter
 * 
 * This file is part of ExtremeML.
 * 
 * ExtremeML is free software: you can redistribute
 * it and/or modify it under the terms of the GNU General Public
 * License, as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 * 
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
 * the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/.
*/
#endregion

using System;
using System.Drawing;
using System.Xml.Linq;
using ExtremeML.Spreadsheet;
using ExtremeML.Spreadsheet.Address;

namespace ExtremeML.Sdk.Vml
{
	/// <summary>
	/// Models the Vml drawing element x:Anchor.
	/// </summary>
	internal class Anchor : VmlElement
	{
		/// <summary>
		/// Initializes a new instance of the <see cref="Anchor"/> class.
		/// </summary>
		/// <param name="element">An <see cref="XElement"/> to which this instance is bound.</param>
		/// <param name="worksheet">The <see cref="WorksheetWrapper"/> that contains the worksheet targeted by the associated comment.</param>
		public Anchor(
			XElement element,
			WorksheetWrapper worksheet)
			: base(element)
		{
			if (worksheet == null) throw new ArgumentNullException("worksheet");

			_worksheet = worksheet;

			Parse();
			Configure(ShapeProperties.GetDefault());
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="Anchor"/> class.
		/// </summary>
		/// <param name="element">An <see cref="XElement"/> to which this instance is bound.</param>
		/// <param name="worksheet">The <see cref="WorksheetWrapper"/> that contains the worksheet targeted by the associated comment.</param>
		/// <param name="cell">A <see cref="GridReference"/> that specifies the location of the associated cell.</param>
		public Anchor(
			XElement element,
			WorksheetWrapper worksheet,
			GridReference cell)
			: base(element)
		{
			if (worksheet == null) throw new ArgumentNullException("worksheet");

			_worksheet = worksheet;

			SetCellPosition(cell);
			Parse();
			ExtractDimensions();
		}

		private readonly WorksheetWrapper _worksheet; // the associated worksheet
		private Point? _cellPosition; // the position of the top right cell corner relative to the worksheet origin
		private Point? _absoluteOrigin; // the shape origin relative to to the worksheet origin
		private Point? _relativeOrigin; // the shape origin relative to the target cell
		private Size? _size;

		// [0] Left column
		// [1] Left offset
		// [2] Top row
		// [3] Top offset
		// [4] Right column
		// [5] Right offset
		// [6] Bottom row
		// [7] Bottom offset
		private int[] _values;

		/// <summary>
		/// Sets the properties of this element and regenerates the underlying Xml.
		/// </summary>
		/// <param name="properties">The properties of the associated shape.</param>
		public void Configure(
			ShapeProperties properties)
		{
			if (properties == null) throw new ArgumentNullException("properties");

			if (properties.Cell.HasValue) SetCellPosition(properties.Cell.Value);
			if (properties.Origin.HasValue) _relativeOrigin = properties.Origin;
			if (properties.Size.HasValue) _size = properties.Size;

			if (_cellPosition.HasValue && _relativeOrigin.HasValue)
			{
				_absoluteOrigin = new Point(_cellPosition.Value.X + _relativeOrigin.Value.X, _cellPosition.Value.Y + _relativeOrigin.Value.Y);
			}

			ApplyDimenions();
			Combine();
		}

		/// <summary>
		/// Populates the values array from the underling Xml data string.
		/// </summary>
		private void Parse()
		{
			_values = new int[8];
			var values = Element.Value.Split(new[] { ',' });

			for (var i = 0; i < 8; i++)
			{
				_values[i] = int.Parse(values[i].Trim());				
			}
		}

		/// <summary>
		/// Rebuilds the underlying Xml string from the values array.
		/// </summary>
		private void Combine()
		{
			var values = new string[8];

			for (var i = 0; i < 8; i++)
			{
				values[i] = _values[i].ToString();
			}

			Element.Value = string.Join(",", values);
		}

		/// <summary>
		/// Populates the dimension fields from the values array in the context of the current worksheet geometry.
		/// </summary>
		private void ExtractDimensions()
		{
			// Horizontal origin
			var left = _values[1];

			for (var i = 0; i < _values[0] + 1; i++)
			{
				left += _worksheet.ColumnSets.GetVisibleWidth(i);
			}

			// Vertical origin
			var top = _values[3];

			for (var i = 0; i < _values[2] + 1; i++)
			{
				top += _worksheet.SheetData.Rows.GetVisibleHeight(i);
			}

			_absoluteOrigin = new Point(left, top);
		
			// Horzontal size
			var right = _values[5];

			for (var i = 0; i < _values[4] + 1; i++)
			{
				right += _worksheet.ColumnSets.GetVisibleWidth(i);
			}

			// Vertical size
			var bottom = _values[7];

			for (var i = 0; i < _values[6] + 1; i++)
			{
				bottom += _worksheet.SheetData.Rows.GetVisibleHeight(i);
			}

			_size = new Size(right - left, bottom - top);

			_relativeOrigin = new Point(_absoluteOrigin.Value.X - _cellPosition.Value.X, _absoluteOrigin.Value.Y - _cellPosition.Value.Y);
		}

		/// <summary>
		/// Updates the values array from the dimension fields in the context of the current worksheet geometry.
		/// </summary>
		private void ApplyDimenions()
		{
			if (_absoluteOrigin.HasValue && _size.HasValue)
			{
				// Left
				_values[0] = 0;
				_values[1] = _absoluteOrigin.Value.X;

				while (true)
				{
					var left = _worksheet.ColumnSets.GetVisibleWidth(_values[0]);

					if (left > _values[1])
					{
						break;
					}

					_values[0]++;
					_values[1] -= left;
				}

				// Top
				_values[2] = 0;
				_values[3] = _absoluteOrigin.Value.Y;

				while (true)
				{
					var top = _worksheet.SheetData.Rows.GetVisibleHeight(_values[2]);

					if (top > _values[3])
					{
						break;
					}

					_values[2]++;
					_values[3] -= top;
				}

				// Right
				_values[4] = 0;
				_values[5] = _absoluteOrigin.Value.X + _size.Value.Width;

				while (true)
				{
					var right = _worksheet.ColumnSets.GetVisibleWidth(_values[4]);

					if (right > _values[5])
					{
						break;
					}

					_values[4]++;
					_values[5] -= right;
				}

				// Bottom
				_values[6] = 0;
				_values[7] = _absoluteOrigin.Value.Y + _size.Value.Height;

				while (true)
				{
					var bottom = _worksheet.SheetData.Rows.GetVisibleHeight(_values[6]);

					if (bottom > _values[7])
					{
						break;
					}

					_values[6]++;
					_values[7] -= bottom;
				}
			}
		}

		/// <summary>
		/// Calculates the associated cell position from the passed grid reference.
		/// </summary>
		private void SetCellPosition(
			GridReference cell)
		{
			// Horizontal position includes the width of the column containing the cell
			var left = 0;

			for (var i = 0; i < cell.ColumnIndex + 1; i++)
			{
				left += _worksheet.ColumnSets.GetVisibleWidth(i);
			}

			// Vertical position excludes the height of the row containing the cell
			var top = 0;

			for (var i = 0; i < cell.RowIndex; i++)
			{
				top += _worksheet.SheetData.Rows.GetVisibleHeight(i);
			}

			_cellPosition = new Point(left, top);
		}
	}
}